package com.ddxcloud.movie.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ddxcloud.common.constants.PayTypeConstant;
import com.ddxcloud.common.constants.enums.OrderStatusEnum;
import com.ddxcloud.movie.config.properties.CommonProperties;
import com.ddxcloud.common.constants.PayMethodConstant;
import com.ddxcloud.common.constants.ResultStatusConstant;
import com.ddxcloud.common.constants.enums.PayStatusEnum;
import com.ddxcloud.common.handler.BusinessException;
import com.ddxcloud.common.utils.IpUtil;
import com.ddxcloud.common.utils.PayOrderUtil;
import com.ddxcloud.movie.dto.ConfirmOrderDTO;
import com.ddxcloud.movie.dto.PayDTO;
import com.ddxcloud.movie.dto.RefundDTO;
import com.ddxcloud.movie.entity.Channel;
import com.ddxcloud.movie.entity.Order;
import com.ddxcloud.movie.entity.PaymentOrder;
import com.ddxcloud.movie.mapper.PaymentOrderMapper;
import com.ddxcloud.movie.service.*;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {

    @Resource
    ChannelService channelService;

    @Resource
    PaymentOrderService paymentOrderService;

    @Resource
    PaymentOrderMapper paymentOrderMapper;

    @Resource
    OrderService orderService;

    @Resource
    MoviesService moviesService;

    @Resource
    WxPayService wxPayService;

    @Resource
    WechatRefundService wechatRefundService;

    @Resource
    CommonProperties commonProperties;

    @Value("${spring.profiles.active}")
    private String active;

    @Override
    public <T> T createOrder(PayDTO dto, HttpServletRequest request) {
        String outTradeNo = dto.getOrderId() + "_" + RandomUtil.randomNumbers(4);
        PaymentOrder paymentOrder = new PaymentOrder();
        paymentOrder.setOrderSn(PayOrderUtil.getOrderSn(PayTypeConstant.Payment));
        paymentOrder.setOrderId(dto.getOrderId());
        paymentOrder.setProductName("电影票");
        paymentOrder.setPayMethod(PayMethodConstant.WECHAT);
        paymentOrder.setPayAmount(commonProperties.isDebug() ? 1 : dto.getAmount());
        paymentOrder.setPayStatus(PayStatusEnum.STATUS_0.getCode());
        paymentOrder.setCallbackUrl(commonProperties.getDomainName() + "/pay/wechat/notify");
        paymentOrder.setClientIp(IpUtil.getClientIp(request));
        paymentOrder.setOutTradeNo(outTradeNo);
        log.info("微信支付-请求参数 {}", JSONUtil.toJsonStr(paymentOrder));
        boolean res = paymentOrderService.save(paymentOrder);
        if (res) {
            try {
                WxPayUnifiedOrderV3Request orderV3Request = new WxPayUnifiedOrderV3Request();
                orderV3Request.setOutTradeNo(outTradeNo);
                orderV3Request.setDescription("电影票");
                orderV3Request.setTimeExpire(DateUtil.format(new Date(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
                orderV3Request.setNotifyUrl(paymentOrder.getCallbackUrl());

                if (dto.getTradeType().equals(TradeTypeEnum.H5)) {
                    WxPayUnifiedOrderV3Request.SceneInfo sceneInfo = new WxPayUnifiedOrderV3Request.SceneInfo();
                    WxPayUnifiedOrderV3Request.H5Info h5Info = new WxPayUnifiedOrderV3Request.H5Info();
                    h5Info.setType("Wap");
                    h5Info.setAppName("订单熊");
                    h5Info.setAppUrl("http://movie.ddx24.com");
                    sceneInfo.setH5Info(h5Info);
                    sceneInfo.setPayerClientIp(IpUtil.getClientIp(request));
                    orderV3Request.setSceneInfo(sceneInfo);
                } else if (dto.getTradeType().equals(TradeTypeEnum.JSAPI)) {
                    WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
                    payer.setOpenid(dto.getOpenId());
                    orderV3Request.setPayer(payer);
                }

                WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
                amount.setTotal(commonProperties.isDebug() && active.equals("dev") ? 1 : dto.getAmount());
                amount.setCurrency("CNY");
                orderV3Request.setAmount(amount);
                log.info("微信支付-响应参数 {}", JSONUtil.toJsonStr(orderV3Request));
                return wxPayService.createOrderV3(dto.getTradeType(), orderV3Request);
            } catch (WxPayException e) {
                log.error("微信支付失败 {}", e.getMessage());
                throw new BusinessException(e.getMessage());
            }
        }
        return null;
    }

    @Override
    public Map<String, String> notify(HttpServletRequest request, HttpServletResponse response) throws WxPayException, IOException {
        SignatureHeader signatureHeader = new SignatureHeader();
        signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
        signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
        signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
        signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
        String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
        WxPayNotifyV3Result notifyV3Result = wxPayService.parseOrderNotifyV3Result(xmlResult, signatureHeader);
        log.info("微信支付-回调 {}", JSONUtil.toJsonStr(notifyV3Result));

        // 取出订单ID
        String orderId = StringUtils.substringBeforeLast(notifyV3Result.getResult().getOutTradeNo(), "_");

        Map<String, String> resultMap = new HashMap<>();
        PaymentOrder paymentOrder = paymentOrderMapper.getOrderByOrderId(orderId);
        if (ObjectUtil.isNotNull(paymentOrder)) {
            if (Objects.equals(paymentOrder.getPayStatus(), PayStatusEnum.STATUS_1.getCode())) {
                resultMap.put("code", ResultStatusConstant.SUCCESS);
                resultMap.put("message", ResultStatusConstant.SUCCESS);
                return resultMap;
            }
            LambdaUpdateWrapper<PaymentOrder> paymentUpdate = new LambdaUpdateWrapper<>();
            paymentUpdate.set(PaymentOrder::getPayStatus, PayStatusEnum.STATUS_1.getCode());
            paymentUpdate.set(PaymentOrder::getPayTime, new Date());
            paymentUpdate.eq(PaymentOrder::getId, paymentOrder.getId());
            boolean res = paymentOrderService.update(paymentOrder, paymentUpdate);
            if (res) {
                Order order = orderService.getById(orderId);
                if (ObjectUtil.isNotEmpty(order)) {
                    Channel channel = channelService.getById(order.getAppId());
                    ConfirmOrderDTO confirmOrder = new ConfirmOrderDTO();
                    confirmOrder.setOrderNo(order.getOutTradeNo());
                    JSONObject result = moviesService.confirmOrder(confirmOrder, channel);
                    if (result.containsKey("code") && result.containsKey("data")) {
                        // 订单确认无论成功与否都执行修改订单状态和回填实际支付金额
                        LambdaUpdateWrapper<Order> orderUpdate = new LambdaUpdateWrapper<>();
                        orderUpdate.set(Order::getPayAmount, new BigDecimal(paymentOrder.getPayAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
                        orderUpdate.set(Order::getOrderStatus, OrderStatusEnum.STATUS_1.getCode());
                        orderUpdate.eq(Order::getOrderId, order.getOrderId());
                        orderService.update(order, orderUpdate);
                        // 订单确认失败时立即执行退款操作
                        if (result.getInteger("code") == 1) {
                            RefundDTO dto = new RefundDTO();
                            dto.setOrderId(order.getOrderId().toString());
                            dto.setPayMethod(PayMethodConstant.WECHAT);
                            dto.setRefundAmount(paymentOrder.getPayAmount());
                            dto.setReason(result.getString("msg"));
                            wechatRefundService.createOrder(dto, request);
                        }
                        resultMap.put("code", ResultStatusConstant.SUCCESS);
                        resultMap.put("message", ResultStatusConstant.SUCCESS);
                    }
                }
            }
        } else {
            response.setStatus(400);
            resultMap.put("code", ResultStatusConstant.FAIL);
            resultMap.put("message", ResultStatusConstant.FAIL);
        }
        return resultMap;
    }
}
